//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
//

#include <omnetpp.h>
#include "Structs.h"
#include "UDPBasicApp.h"
#include "Messages_m.h"

class TrackerApp : public UDPBasicApp {
	
	IPvXAddress addr;
	std::string s_addr;
	TrackerTable *streams;
	std::vector<std::string> cdns;
	int id;
	bool ranked;
	
	protected:
		virtual void initialize(int);
		virtual void handleMessage (cMessage *msg);
		void processAdvertisement (Advertisement *);
		void processPeerRequest (PeerRequest *);
		void disconnectPeer(PeerExit *);

	public:
		TrackerApp();
};

Define_Module(TrackerApp);

void TrackerApp::initialize(int stage) {

	// because of IPAddressResolver, we need to wait until interfaces are registered,
	// address auto-assignment takes place etc.
	if (stage!=3)
        	return;

	counter = 0;
	numSent = 0;
	numReceived = 0;
	WATCH(numSent);
	WATCH(numReceived);

	localPort = par("localPort");
	destPort = par("destPort");

	const char *destAddrs = par("destAddresses");
	cStringTokenizer tokenizer(destAddrs);
	const char *token;
	while ((token = tokenizer.nextToken())!=NULL)
        	destAddresses.push_back(IPAddressResolver().resolve(token));

	//if (destAddresses.empty())
        //	return;

	bindToPort(localPort);

	id = par("id");
	ranked = par ("rank");	
	streams = new TrackerTable();
	cMessage *c = new cMessage("ConfigureMsg", CONFIGURE);
	scheduleAt(simTime() + 1,c);
	WATCH_VECTOR(cdns);
}

TrackerApp::TrackerApp() {
	UDPBasicApp::UDPBasicApp();
}

void TrackerApp::processAdvertisement(Advertisement *padv) {
	if (padv->getStreamID() == CDN_FLAG) {
		//cdn
		cdns.push_back(padv->getSrcAddr());
		ev << getFullPath() << ": Register host " << padv->getSrcAddr() << " cdn " << padv->getPeerID() << "\n";
	}
	else {
		//host peer
		streams->insertEntry(padv->getPeerID(),padv->getStreamID(), padv->getSrcAddr());
		ev << getFullPath() << ": Register host " << padv->getSrcAddr() << " peer " << padv->getPeerID() << "\n";
	}
}

void TrackerApp::processPeerRequest(PeerRequest *preq) {
	
	IPvXAddress dst;
	srand(time(NULL));
	std::vector<std::string> c = cdns;
	unsigned k;
	unsigned i;
	unsigned j;
	unsigned size;
	
	ev << getFullPath() << ": Received request from host " << preq->getSrcAddr() << " peer " << preq->getPeerID() << "\n";
	//Buscar o stream na tabela de streams/peers
	PeerList *plist = new PeerList();
	plist->setPeerID(id);
	plist->setStreamID(preq->getStreamID());
	plist->setSrcAddr(s_addr.c_str());
	plist->setDstAddr(preq->getSrcAddr());
	//Adicionar enderecos dos peers a lista
	std::vector<std::string> v = streams->getPeersAddrs(preq->getStreamID());
	
	if (!ranked) {
		//Preenche a lista com peers e completa com cdns, se necessario
		plist->setPeerListArraySize(v.size());
		size = v.size();
		for (i = 0; i < size; i++) {
			k = random()%v.size();
			plist->setPeerList(i,v[k].c_str());
			v.erase(v.begin()+k);
		}

		if (size < TRACKER_MIN_PEERS) {
			//Nao ha peers suficientes: recorre as cdns
			plist->setPeerListArraySize(TRACKER_MIN_PEERS);
			j = 0;
			
			for (i = size; i < plist->getPeerListArraySize(); i++)
				if (j >= cdns.size()) {
					plist->setPeerListArraySize(size+cdns.size());
					break;
				}
				else {
					k = random()%c.size();
					plist->setPeerList(i,c[k].c_str());
					c.erase(c.begin()+k);
					j++;
				}
		}
	}
	else {
		//Preenche a lista com cdns, de acordo com o rank, e completa
		int rank = preq->getRank();
		if (rank < 0)
			rank = 0;
		if (rank > MAX_RANK)
			rank = MAX_RANK;
		
		j = rank / 10;
		
		//Envia ao menos um provedor de conteudo
		if ( (v.size() == 0) and (j == 0) and (cdns.size() > 0))
			j++;
		
		//Caso nao haja cdns suficientes
		if (j > cdns.size())
			j = cdns.size();
		
		plist->setPeerListArraySize(v.size()+j);
		//Comeca com as cdns
		for (i = 0; i < j; i++) {
			k = random()%c.size();
			plist->setPeerList(i,c[k].c_str());
			c.erase(c.begin()+k);
		}
		
		size = v.size();
		//Termina com os peers
		for (i = 0; i < size; i++) {
			k = random()%v.size();
			plist->setPeerList(i+j,v[k].c_str());
			v.erase(v.begin()+k);
		}
	}
	
	ev << getFullPath() << ": Send list to " << preq->getSrcAddr() << " Peer " << preq->getPeerID() << "\n";
	if (dst.tryParse(preq->getSrcAddr())) {
		sendToUDP (plist, localPort, dst, destPort);
	}
	else {
		ev << getFullPath() << ": IPxAddress Parse error " << preq->getSrcAddr() << "\n";		
		delete plist;
	}

}

void TrackerApp::disconnectPeer(PeerExit *pext) {
	streams->removePeer(pext->getPeerID());
}

void TrackerApp::handleMessage(cMessage *msg) {
	
	General *gmsg;
	int type;
	
	if (msg->isSelfMessage()) {
		switch (msg->getKind()) {
			case CONFIGURE:
				addr = IPAddressResolver().resolve(getParentModule()->getFullName());
				s_addr = addr.str();
				//ev << "\n-----\nNome " << getParentModule()->getFullName() << "\n Address "<< addr << "\n-------\n";
				break;
			
			default:
				break;
				
		}
	}
	else {
		//gmsg = (General *) msg;
		if (dynamic_cast<General *> (msg) != NULL) {
			gmsg = (General *) msg;
			type = gmsg->getType();
		}
		else
			type = -1;
			
		switch (type) {
			case ADVERTISEMENT:
				//Registro de peer ou CDN
				processAdvertisement((Advertisement *) gmsg);
				break;
			case PEERREQ:
				//Pedido de lista de peers
				processPeerRequest((PeerRequest *) gmsg);
				break;
			case PEEREXIT:
				//Desconexão de um peer
				disconnectPeer((PeerExit *) gmsg);
				break;
			default:
				break;
		}
	}
	delete msg;
}

